#ifndef XG_REDISCONNECT_CPP
#define XG_REDISCONNECT_CPP
////////////////////////////////////////////////////////
#include "../RedisConnect.h"


string RedisConnect::Command::toString() const
{
	StringCreator out;

	out << "*" << vec.size() << "\r\n";

	for (const string& item : vec)
	{
		out << "$" << item.length() << "\r\n" << item << "\r\n";
	}

	return out.getContent();
}
int RedisConnect::Command::parse(const char* msg, int len)
{
	if (*msg == '$')
	{
		const char* end = parseNode(msg, len);

		if (end == NULL) return XG_DATAERR;

		switch (end - msg)
		{
			case 0: return XG_TIMEOUT;
			case -1: return XG_NOTFOUND;
		}

		return XG_OK;
	}

	const char* str = msg + 1;
	const char* end = strstr(str, "\r\n");

	if (end == NULL) return XG_TIMEOUT;

	if (*msg == '+' || *msg == '-' || *msg == ':')
	{
		this->status = XG_OK;
		this->msg = string(str, end);

		if (*msg == '+') return XG_OK;
		if (*msg == '-') return XG_ERROR;

		this->status = stdx::atoi(str);

		return XG_OK;
	}

	if (*msg == '*')
	{
		int cnt = stdx::atoi(str);
		const char* tail = msg + len;

		vec.clear();
		str = end + 2;

		while (cnt > 0)
		{
			if (*str == '*') return parse(str, tail - str);

			end = parseNode(str, tail - str);

			if (end == NULL) return XG_DATAERR;

			if (end == str) return XG_TIMEOUT;

			str = end;
			cnt--;
		}

		return res.size();
	}

	return XG_DATAERR;
}
const char* RedisConnect::Command::parseNode(const char* msg, int len)
{
	const char* str = msg + 1;
	const char* end = strstr(str, "\r\n");

	if (end == NULL) return msg;

	int sz = stdx::atoi(str);

	if (sz < 0) return msg + sz;

	str = end + 2;
	end = str + sz + 2;

	if (msg + len < end) return msg;

	res.push_back(string(str, str + sz));

	return end;
}
int RedisConnect::Command::getResult(RedisConnect* redis, int timeout)
{
	auto doWork = [&](){
		string msg = toString();
		Socket& sock = redis->sock;

		if (sock.write(msg.c_str(), msg.length()) < 0) return XG_NETERR;

		int len = 0;
		int delay = 0;
		int readed = 0;
		char* dest = redis->buffer.str();
		const int maxsz = redis->buffer.size();

		while (readed < maxsz)
		{
			if ((len = sock.read(dest + readed, maxsz - readed, false)) < 0) return len;

			if (len == 0)
			{
				delay += SOCKECT_RECVTIMEOUT;

				if (delay > timeout) return XG_TIMEOUT;
			}
			else
			{
				dest[readed += len] = 0;

				if ((len = parse(dest, readed)) == XG_TIMEOUT)
				{
					delay = 0;
				}
				else
				{
					return len;
				}
			}
		}

		return XG_PARAMERR;
	};

	status = 0;
	msg.clear();

	redis->code = doWork();

	if (redis->code < 0 && msg.empty())
	{
		switch (redis->code)
		{
		case XG_SYSERR:
			msg = "system error";
			break;
		case XG_NETERR:
			msg = "network error";
			break;
		case XG_DATAERR:
			msg = "protocol error";
			break;
		case XG_TIMEOUT:
			msg = "response timeout";
			break;
		case XG_NOTFOUND:
			msg = "element not found";
			break;
		default:
			msg = "unknown error";
			break;
		}
	}

	redis->status = status;
	redis->msg = msg;

	return redis->code;
}

static RedisConnect* GetTemplate()
{
	XG_DEFINE_GLOBAL_VARIABLE(RedisConnect)
}
bool RedisConnect::CanUse()
{
	return GetTemplate()->port > 0;
}
sp<RedisConnect> RedisConnect::Instance()
{
	return GetTemplate()->grasp();
}
void RedisConnect::Setup(const string& host, int port, const string& password, int timeout, int memsz)
{
	RedisConnect* redis = GetTemplate();

	redis->host = host;
	redis->port = port;
	redis->memsz = memsz;
	redis->timeout = timeout;
	redis->password = password;
}
sp<RedisConnect> RedisConnect::grasp() const
{
	static ResPool<RedisConnect> pool([&](){
		sp<RedisConnect> redis = newsp<RedisConnect>();

		if (redis->connect(host, port, timeout, memsz) && redis->auth(password)) return redis;

		return redis = NULL;
	});

	sp<RedisConnect> redis = pool.get();

	if (redis)
	{
		if (redis->code >= 0 || redis->code == XG_ERROR || redis->code == XG_NOTFOUND) return redis;

		pool.disable(redis);

		return grasp();
	}

	return redis;
}

int RedisConnect::auth(const string& password)
{
	this->password = password;

	if (password.empty()) return XG_OK;

	return execute("auth", password);
}
int RedisConnect::get(const string& key, string& val)
{
	vector<string> vec;

	if (execute(vec, "get", key) <= 0) return code;

	val = vec[0];

	return code;
}
int RedisConnect::lpop(const string& key, string& val)
{
	vector<string> vec;

	if (execute(vec, "lpop", key) <= 0) return code;

	val = vec[0];

	return code;
}
int RedisConnect::rpop(const string& key, string& val)
{
	vector<string> vec;

	if (execute(vec, "rpop", key) <= 0) return code;

	val = vec[0];

	return code;
}
string RedisConnect::hget(const string& key, const string& filed)
{
	string res;

	hget(key, filed, res);

	return std::move(res);
}
int RedisConnect::hget(const string& key, const string& filed, string& val)
{
	vector<string> vec;

	if (execute(vec, "hget", key, filed) <= 0) return code;

	val = vec[0];

	return code;
}
int RedisConnect::hgetall(const string& key, map<string, string>& resmap)
{
	vector<string> vec;

	if (execute(vec, "hgetall", key) <= 0) return code;

	int len = vec.size();

	if (len % 2) return XG_DATAERR;

	for (int i = 0; i < len; i += 2)
	{
		resmap[vec[i]] = vec[i + 1];
	}

	return code;
}

string RedisConnect::getLockId()
{
	thread_local string id;

	if (id.empty())
	{
		u_long num = 0;
		vector<string> host;

		if (Socket::GetLocalAddress(host) > 0) num = GetHostInteger(host.front().c_str());

		stdx::append(id, "%ld:%ld:%ld", num, (u_long)Process::GetCurrentProcess(), (u_long)GetCurrentThreadId());
	}

	return id;
}
bool RedisConnect::unlock(const string& key)
{
	const char* lua = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

	return eval(lua, key, getLockId()) > 0 && status == XG_OK;
}
bool RedisConnect::lock(const string& key, int timeout)
{
	int delay = timeout * 1000;

	for (int i = 0; i < delay; i += 10)
	{
		if (execute("set", key, getLockId(), "nx", "ex", timeout) >= 0) return true;

		Sleep(10);
	}

	return false;
}
int RedisConnect::incr(const string& key, int val, int timeout)
{
	if (timeout <= 0) return execute("incrby", key, val);

	const char* lua = "local res = redis.call('incrby',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return res;";

	int res = eval(lua, key, val, timeout);

	return res < 0 ? res : getStatus();
}

void RedisSession::close()
{
	datamap.clear();
	redis = NULL;
}
bool RedisSession::clear()
{
	long timeout = getTimeout();

	CHECK_FALSE_RETURN(timeout > 0 && disable());

	return setTimeout(timeout);
}
bool RedisSession::update()
{
	map<string, string> tmp;

	CHECK_FALSE_RETURN(redis->hgetall(name, tmp) >= 0);
	CHECK_FALSE_RETURN(tmp.size() > 0);

	std::swap(datamap, tmp);

	return true;
}
bool RedisSession::disable()
{
	CHECK_FALSE_RETURN(redis->del(name) >= 0);

	datamap.clear();

	return true;
}
int RedisSession::size() const
{
	return datamap.size();
}
long RedisSession::getTimeout() const
{
	return redis->ttl(name);
}
bool RedisSession::setTimeout(long second)
{
	const char* lua = "redis.call('hset',KEYS[1],'${ctime}',ARGV[2]); return redis.call('expire',KEYS[1],ARGV[1]);";

	return redis->eval(lua, name, second, time(NULL)) >= 0;
}
bool RedisSession::remove(const string& key)
{
	CHECK_FALSE_RETURN(redis->hdel(name, key) >= 0);

	datamap.erase(key);

	return true;
}
bool RedisSession::init(const string& name, int timeout)
{
	CHECK_FALSE_RETURN(redis = RedisConnect::Instance());

	this->name = name;

	if (timeout <= 0) return update();

	return setTimeout(timeout) && update();
}
bool RedisSession::set(const map<string, string>& attrmap)
{
	RedisConnect::Command cmd("hmset");

	for (const auto& item : attrmap)
	{
		cmd.add(item.first);
		cmd.add(item.second);
	}

	if (redis->execute(cmd) < 0) return false;

	for (const auto& item : attrmap)
	{
		datamap[item.first] = item.second;
	}

	return true;
}
bool RedisSession::set(const string& key, const string& val)
{
	CHECK_FALSE_RETURN(redis->hset(name, key, val) >= 0);

	datamap[key] = val;

	return true;
}
bool RedisSession::get(const string& key, string& val) const
{
	auto it = datamap.find(key);

	if (it == datamap.end()) return false;

	val = it->second;

	return true;		
}
////////////////////////////////////////////////////////
#endif